package bsh.dyn;

import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import bsh.CompilationState;
import bsh.CompilationUnit;
import bsh.CompiledScript;
import bsh.exception.CompilationFailedException;
import bsh.utils.reflection.BytecodeHelper;

/**
 * Simple implementation of {@link ArgumentsAdaptor}
 * 
 * @author takeshi
 */
public class ArgumentsAdaptorImpl implements ArgumentsAdaptor {

    private final Class<?>[] targetTypes;

    private final TransformationList[] transformations;

    private final int weight;

    /**
     * C'tor
     * 
     * @param targetTypes array of {@link Class}
     * @param tranformations array of {@link TransformationList}
     */
    public ArgumentsAdaptorImpl(Class<?>[] targetTypes, TransformationList[] tranformations) {
        this.targetTypes = targetTypes;
        this.transformations = tranformations;
        for (TransformationList transformationList : tranformations) {
            if (transformationList == null) {
                throw new NullPointerException();
            }
        }
        this.weight = calculateWeight(tranformations);
    }

    /**
     * {@inheritDoc}
     */
    public Class<?>[] targetTypes() {
        Class<?>[] copy = new Class<?>[this.targetTypes.length];
        System.arraycopy(targetTypes, 0, copy, 0, copy.length);
        return copy;
    }

    /**
     * {@inheritDoc}
     */
    public Object[] adaptArguments(CompiledScript script, Object[] original) {
        Object[] result = new Object[transformations.length];
        adaptArgumentsInto(script, original, result);
        return result;
    }

    /**
     * Calculates the weight of a series of {@link TransformationList}
     * 
     * @param transformations array of {@link TransformationList}
     * @return int
     */
    public static int calculateWeight(TransformationList[] transformations) {
        int weight = 0;
        for (TransformationList txList : transformations) {
            if (txList.getWeight() == Integer.MAX_VALUE) {
                return Integer.MAX_VALUE;
            }
            weight += txList.getWeight();
        }
        return weight;
    }

    /**
     * {@inheritDoc}
     */
    public int weight() {
        return weight;
    }

    /**
     * {@inheritDoc}
     */
    public void adaptArgumentsInto(CompiledScript script, Object[] original, Object[] destination) {
        for (int i = 0; i < transformations.length; i++) {
            destination[i] = transformations[i].transform(original[i], script);
        }
    }

    /**
     * {@inheritDoc}
     */
    public boolean isCompilable() {
        for (TransformationList list : this.transformations) {
            if (!list.isCompilable()) {
                return false;
            }
        }
        return true;
    }

    /**
     * {@inheritDoc}
     */
    public void generateBytecode(MethodVisitor visitor, CompilationUnit unit, CompilationState state) throws CompilationFailedException {
        int limit = targetTypes.length;
        TransformationList[] txs = this.transformations;
        transformParameters(visitor, state, limit, txs);
    }

    /**
     * Loads parameters and transforms it in order to call the method
     * 
     * @param visitor the {@link MethodVisitor}
     * @param state {@link CompilationState}
     * @param limit parameters with index < limit and >= 0 are loaded and transformed this way
     * @param txs array of transformations to be performed
     * @throws CompilationFailedException e
     */
    static void transformParameters(MethodVisitor visitor, CompilationState state, int limit, TransformationList[] txs)
            throws CompilationFailedException {
        for (int i = 0; i < limit; i++) {
            loadParameterWithIndex(visitor, i, state);
            // now the stack has something
            txs[i].compile(visitor, state);
        }
    }

    /**
     * Loads an object from the parameters array with index i.
     * 
     * @param visitor the {@link MethodVisitor}
     * @param i the parameter index
     * @param state {@link CompilationState}
     */
    static void loadParameterWithIndex(MethodVisitor visitor, int i, CompilationState state) {
        visitor.visitVarInsn(Opcodes.ALOAD, CompilableTransformation.METHOD_CALL_PARAMETERS_INDEX);
        BytecodeHelper.pushIntConst(i, visitor);
        visitor.visitInsn(Opcodes.AALOAD);
        state.pushStack(Object.class);
    }

}
